#include <nds.h>
#include <dswifi9.h>

#include <cmath>
#include <ctime>
#include <cstring>

#include "debug.h"
#include "player.h"
#include "graphics.h"
#include "scrobbler.h"
#include "command.h"

#undef SOUND_FREQ
#define SOUND_FREQ(n)		(0x10000 - (16777216 / (n)))

extern Graphics graphics;
extern Scrobbler scrobbler;

// ======================================
// Normal methods
// ======================================

// ----------------
Player::Player() {
// ----------------
	curPlaying = 0;
	vol = 127;
	mode = PLAYER_NORMAL;
	progress.now = progress.total = 0;
	scrobbled = false;
}

// ----------------
PlayerStatus Player::getStatus() {
// ----------------
	return status;
}

// ----------------
fileTime Player::getTime() {
// ----------------
	return progress;
}

// ---------------
void Player::setMode(PlayerMode newmode)
// ---------------
{
	mode = newmode;
}

// ======================================
// File read related methods
// ======================================

// ----------------
void Player::load() {
// ----------------
	#ifdef DEBUG_PLAYER
	iprintf("player::load\n");
	#endif
	if (playlist.getCurNo() > 0) {
		curPlaying = playlist.getCurNo();
		stop();

		info = playlist.getInfo(curPlaying);
		decoder = DecoderFactory::getDecoderByType(info.type);
		decoder->prprForPlayback(info);
		decoder->toFirstSample();
		
		#ifndef DEBUG
		graphics.setTags(info);
		#endif
		
		progress.total = info.length/info.sampleRate;
		progress.min = progress.total/60;
		progress.sec = progress.total%60;
		progress.now = 0;
		status = PLAYER_STOP;

		buffer_size = info.sampleRate/PLAYER_BUFFER_DIV;
		clearBuffer();
		fillBuffer();
		
		#ifndef DEBUG
		graphics.setInfo(info);
		#endif
	}
}

// ----------------
void Player::playSample() {
// ----------------
	if (!buffer_flag) {
		ARM7updateChannel(0, (const void*)buffer_L1, vol);
		ARM7updateChannel(1, (const void*)buffer_R1, vol);
	} else {
		ARM7updateChannel(0, (const void*)buffer_L2, vol);
		ARM7updateChannel(1, (const void*)buffer_R2, vol);
	}
	buffer_flag = !buffer_flag;
	fillBuffer();
}

// ----------------
void Player::fillBuffer() {
// ----------------
	if (!decoder->reachedEnd()) {
		if (buffer_flag) {
			decoder->fillBuffers(buffer_L1, buffer_R1, buffer_size);
		} else {
			decoder->fillBuffers(buffer_L2, buffer_R2, buffer_size);
		}
	} else {
		#ifdef DEBUG_PLAYER
		iprintf("player::reachedEnd\n");
		#endif
		stop();
		if (playlist.getCurNo() < playlist.numberOfEntries()) {
			playlist.next();
			play();
		} else if (mode == PLAYER_REPEAT) {
			playlist.setCurrent(1);
			play();
		} else {
			scrobbler.nowplaying(NULL);
			scrobbler.process();
			scrobbler.writeScrobbls();
		}
	}
}

// ----------------
void Player::clearBuffer()
// ----------------
{
	#ifdef ARM9
		dmaFillHalfWords(0, buffer_L1, PLAYER_BUFFER_MAX);
		dmaFillHalfWords(0, buffer_R1, PLAYER_BUFFER_MAX);
		dmaFillHalfWords(0, buffer_L2, PLAYER_BUFFER_MAX);
		dmaFillHalfWords(0, buffer_R2, PLAYER_BUFFER_MAX);
	#else
		memset(buffer_L1, 0, (PLAYER_BUFFER_MAX<<1));
		memset(buffer_R1, 0, (PLAYER_BUFFER_MAX<<1));
		memset(buffer_L2, 0, (PLAYER_BUFFER_MAX<<1));
		memset(buffer_R2, 0, (PLAYER_BUFFER_MAX<<1));
	#endif
}

// ======================================
// File read related methods
// ======================================

// ----------------
void Player::play() {
// ----------------
	#ifdef DEBUG_PLAYER
	iprintf("player::play\n");
	#endif
	if (curPlaying != playlist.getCurNo()) {
		scrobbler.writeScrobbls();
		swiWaitForVBlank();
		load();
	}
	
	bool scblprcs = false;	
	if (status == PLAYER_STOP) {
		scrobbler.nowplaying(&info);
		startTime = time(NULL);
		scblprcs = true;
	}
	
	if (status == PLAYER_STOP || status == PLAYER_PAUSE) {
		// 16-bit
		ARM7setChannel(0, info.sampleRate, buffer_size*2, 0);
		ARM7setChannel(1, info.sampleRate, buffer_size*2, 127);
		
		status = PLAYER_PLAY;
		timerstart();
	}
	if (scblprcs) scrobbler.process();

	#ifdef DEBUG_FILEOUT
		DBGFILE = fopen("dbgout.pcm", "w");
	#endif
}

// ----------------
void Player::togglepause() {
// ----------------
	#ifdef DEBUG_PLAYER
	iprintf("player::pause\n");
	#endif
	if (status == PLAYER_PLAY) {
		timerstop();
		ARM7stopChannel(0);
		ARM7stopChannel(1);
		status = PLAYER_PAUSE;
	} else if (status == PLAYER_PAUSE) {
		play();
	}
}

// ----------------
void Player::toggleplay() {
// ----------------
	#ifdef DEBUG_PLAYER
	iprintf("player::toggleplay\n");
	#endif
	if (status == PLAYER_PLAY) {
		togglepause();
	} else {
		play();
	}
}

// ----------------
void Player::stop() {
// ----------------
	#ifdef DEBUG_PLAYER
	iprintf("player::stop\n");
	#endif
	timerstop();
	ARM7stopChannel(0);
	ARM7stopChannel(1);
	if (status != PLAYER_INITIAL) decoder->toFirstSample();
	progress.now = progress.curmin = progress.cursec = 0;
	clearBuffer();
	status = PLAYER_STOP;
	#ifndef DEBUG
	graphics.setTimer(progress);
	#endif
	if (scrobbled)
	{
		scrobbler.submission(&info, &startTime);
	}
	scrobbled = false;

	#ifdef DEBUG_FILEOUT
		fclose(DBGFILE);
	#endif
}

// ----------------
void Player::prev() {
// ----------------
	#ifdef DEBUG_PLAYER
	iprintf("player::prev\n");
	#endif
	if (playlist.getCurNo() > 1) {
		bool tmp;
		if (status == PLAYER_PLAY) tmp = true;
			else tmp = false;
		if (tmp) stop();
		playlist.prev();
		if (tmp) play();
	}
}

// ----------------
void Player::next() {
// ----------------
	#ifdef DEBUG_PLAYER
	iprintf("player::next\n");
	#endif
	if (playlist.getCurNo() < playlist.numberOfEntries()) {
		bool tmp;
		if (status == PLAYER_PLAY) tmp = true;
			else tmp = false;
		if (tmp) stop();
		playlist.next();
		if (tmp) play();
	}
}

// ----------------
void Player::volUp() {
// ----------------
	if (vol < 127) ++vol;
}

// ----------------
void Player::volDown() {
// ----------------
	if (vol > 0) --vol;
}

// ======================================
// Timer related methods
// ======================================

// ----------------
void Player::timerstart() {
// ----------------
	#ifdef DEBUG_PLAYER
	iprintf("player::timerstart\n");
	#endif
	
	int tmp = info.sampleRate*info.channels;
	TIMER0_DATA = SOUND_FREQ(tmp);

	tmp = buffer_size*info.channels*2;
	TIMER1_DATA = 0x10000 - tmp;

	TIMER0_CR = TIMER_DIV_1 | TIMER_ENABLE;
	TIMER1_CR = TIMER_CASCADE | TIMER_IRQ_REQ | TIMER_ENABLE;
	flag = false;
}

// ----------------
void Player::timerstop() {
// ----------------
	#ifdef DEBUG_PLAYER
	iprintf("player::timerstop\n");
	#endif
	TIMER0_CR = 0;
	TIMER1_CR = 0;
	TIMER0_DATA = 0;
	TIMER1_DATA = 0;
}

// ----------------------
bool Player::process() {
// ----------------------
	if (status != PLAYER_PLAY)
	{
		return false;
	}

	swiIntrWait(1, IRQ_TIMER1);

	playSample();
	progress.now = decoder->getCurrentFrame()/info.sampleRate;
	progress.curmin = progress.now/60;
	progress.cursec = progress.now%60;
	#ifndef DEBUG
	graphics.setTimer(progress);
	#endif
	if (progress.now >= 240 || progress.now >= (progress.total >> 1) )
	{
		if (progress.total > 30) scrobbled = true;
	}
	return true;
}

// -----------------------------------
void Player::ARM7setChannel(int channel, int frequency, int length, u8 pan)
// -----------------------------------
{
	Command command;
	command.commandType = ARM7_SET_CHANNEL;
	
	command.setChannel.channel = channel;
	command.setChannel.frequency = frequency;
	command.setChannel.length = length;
	command.setChannel.pan = pan;
	
	fifoSendDatamsg(FIFO_USER_01, sizeof(Command), (u8*)(&command));
}

// -----------------------------------
void Player::ARM7updateChannel(int channel, const void* data, int volume)
// -----------------------------------
{
	Command command;
	command.commandType = ARM7_UPDATE_CHANNEL; 
	
	command.updateChannel.channel = channel;
	command.updateChannel.data = data;
	command.updateChannel.volume = volume;
	
	fifoSendDatamsg(FIFO_USER_01, sizeof(Command), (u8*)(&command));
}

// -----------------------------------
void Player::ARM7stopChannel(int channel)
// -----------------------------------
{
	Command command;
	command.commandType = ARM7_STOP_CHANNEL; 
	
	command.stopChannel = channel;
	
	fifoSendDatamsg(FIFO_USER_01, sizeof(Command), (u8*)(&command));
}

